#include "kpextCommandBuffer.h"

using namespace kpext;


CommandBuffer::CommandBuffer(std::shared_ptr<vk::Device> device,
                             std::shared_ptr<vk::Queue> queue,
                             uint32_t queueIndex,
                             vk::CommandPoolCreateFlags poolflag,
                             bool isPrimary,
                             uint32_t totalTimestamps,
                             std::shared_ptr<vk::CommandPool> cmdpool
                             ):
    m_device(nullptr),
    m_queue(nullptr),
    m_mutexQueue(nullptr),
    m_queueIndex(queueIndex),
    m_FreeCommandPool(false),
    m_CommandPool(nullptr),
    m_FreeCommandBuffer(false),
    m_CommandBuffer(nullptr),
    m_Fence(nullptr),
    m_IsRunning(false),
    m_IsRecording(false)
{
    UNUSE_VAR(totalTimestamps);

    m_bufLevel = vk::CommandBufferLevel::ePrimary;
    if(!isPrimary){
        m_bufLevel = vk::CommandBufferLevel::eSecondary;
    }

    if(!device || ! queue){
        throw std::runtime_error(__FUNCTION__ "construct with null vulkan context pointer");
    }

    m_device = device;
    m_queue = queue;

    m_Fence = m_device->createFence(vk::FenceCreateInfo());
    m_device->resetFences(m_Fence);

    if(cmdpool){
        m_CommandPool = cmdpool;
        m_FreeCommandPool = true;
    }else{
        CommandBuffer::createCommandPool(poolflag);
    }

    if(m_CommandPool){
        CommandBuffer::createCommandBuffer();
    }

    if(CommandBuffer::isCommandBufferOk()){
        SPDLOG_DEBUG("vulkan commandpool and commandbuffer created");
    }else{
        SPDLOG_ERROR("vulkan commandpool and commandbuffer create failed!");
    }
}

CommandBuffer::~CommandBuffer()
{
    m_mutexQueue = nullptr;
    if(!m_device || ! m_queue) return;

    if(CommandBuffer::isCommandBufferOk()
            && m_bufLevel == vk::CommandBufferLevel::ePrimary
            ){
        if(!CommandBuffer::isExecCompleted()){
            SPDLOG_WARN("commands submited but not complete,"
                        " wait 10s to deconstruct kpext::CommandBuffer");
            waitExec(10000);
        }
    }

    m_device->destroyFence(m_Fence);

    if(m_CommandPool && m_CommandBuffer && m_FreeCommandBuffer){
        m_device->freeCommandBuffers(*m_CommandPool, 1, m_CommandBuffer.get());
        m_CommandBuffer = nullptr;
        m_FreeCommandBuffer = false;
        SPDLOG_DEBUG("vulkan commandbuffer destroyed");
    }

    if(m_CommandPool.use_count() == 1 && m_FreeCommandPool){
        m_device->destroyCommandPool(*m_CommandPool);
        m_CommandPool = nullptr;
        m_FreeCommandPool = false;
        SPDLOG_DEBUG("vulkan commandpool destroyed");
    }else{
        m_CommandPool = nullptr;
    }

    if (m_device) {
        m_device = nullptr;
    }

    if (m_queue) {
        m_queue = nullptr;
    }

}

void CommandBuffer::setQueueAccessMutex(std::shared_ptr<std::mutex> mutex)
{
    m_mutexQueue = mutex;
}

bool CommandBuffer::isCommandBufferOk()
{
    bool ret = m_device && m_queue && m_CommandPool && m_CommandBuffer;
    return ret;
}

bool CommandBuffer::isPrimaryLevel()
{
    return (m_bufLevel == vk::CommandBufferLevel::ePrimary);
}

bool CommandBuffer::isSecondaryLevel()
{
    return (m_bufLevel == vk::CommandBufferLevel::eSecondary);
}

bool CommandBuffer::isSameCmdPool(const CommandBuffer &cmdbuf)
{
    return (cmdbuf.m_CommandPool.get() == this->m_CommandPool.get());
}

bool CommandBuffer::isSameQueue(const CommandBuffer &cmdbuf)
{
    return (cmdbuf.m_queue.get() == this->m_queue.get());
}

std::shared_ptr<CommandBuffer> CommandBuffer::allocateCmdBuf(bool isPrimary,
                                                             vk::CommandPoolCreateFlags poolflag,
                                                             uint32_t totalTimestamps)
{
    if(!isCommandBufferOk()){
        SPDLOG_ERROR("can not allocate command buffer from a invalid command buffer!");
        return nullptr;
    }

    auto cmdbufptr =
            std::make_shared<kpext::CommandBuffer>(
                m_device,
                m_queue,
                m_queueIndex,
                poolflag,
                isPrimary,
                totalTimestamps,
                m_CommandPool
                );

    if(cmdbufptr){
        cmdbufptr->setQueueAccessMutex(
                    this->m_mutexQueue
                    );
    }

    return cmdbufptr;
}

vk::CommandBuffer &CommandBuffer::getVKCmdBuf()
{
    return *m_CommandBuffer;
}

std::shared_ptr<vk::CommandPool> CommandBuffer::getVKCmdPool()
{
    return m_CommandPool;
}

void CommandBuffer::begin()
{
    if(m_IsRecording) return;
    if(!isCommandBufferOk()) return;
    if(!isExecCompleted()){
        SPDLOG_ERROR("latest vulkan command buffer submited but not complete!");
        return;
    }

    SPDLOG_INFO("command buffer started recording");
    if(isSecondaryLevel()){
        // CommandBufferInheritanceInfo必须要有，否则不能正常记录次要命令缓冲到主要命令缓冲
        auto inhertinfo = vk::CommandBufferInheritanceInfo();
        // eSimultaneousUse 标记可重新发送， 如果不标记容易使primary cmdbuf无效化
        auto begininfo = vk::CommandBufferBeginInfo(
                    vk::CommandBufferUsageFlagBits::eSimultaneousUse,
                    &inhertinfo
                    );
        m_CommandBuffer->begin(begininfo);
    }else{
        m_CommandBuffer->begin(vk::CommandBufferBeginInfo());
    }

    m_IsRecording = true;
}

void CommandBuffer::end()
{
    if(!m_IsRecording) return;
    if(!isCommandBufferOk()) return;

    SPDLOG_INFO("command buffer stoped recording");
    m_CommandBuffer->end();
    m_IsRecording = false;
}

void CommandBuffer::submit()
{
    if(!isCommandBufferOk()) return;

    if(!isExecCompleted()){
        SPDLOG_ERROR("latest vulkan command buffer submited but not complete!");
        return;
    }

    if(m_bufLevel == vk::CommandBufferLevel::eSecondary){
        SPDLOG_ERROR("vulkan secondary command buffer can not submited command!");
        return;
    }

    if(m_IsRecording){
        end();
    }

    std::shared_ptr<std::lock_guard<std::mutex>> lock(nullptr);
    if(m_mutexQueue){
        //        m_mutexQueue->lock();
        lock = std::make_shared<std::lock_guard<std::mutex>>(*m_mutexQueue);
    }

    vk::SubmitInfo submitInfo(
      0, nullptr, nullptr, 1, m_CommandBuffer.get());

    m_device->resetFences(m_Fence);
    m_queue->submit(1, &submitInfo, m_Fence);

    //    if(m_mutexQueue) m_mutexQueue->unlock();
    lock = nullptr;

    m_IsRunning = true;

    SPDLOG_INFO("submit command to commandpool");
}

bool CommandBuffer::isExecCompleted()
{
    if(!CommandBuffer::isCommandBufferOk()) return true;

    bool ret = false;
    auto res = m_device->getFenceStatus(m_Fence);
    if(res == vk::Result::eSuccess){
        ret = true;
        m_IsRunning = false;
    }

    if(!m_IsRunning) return true;

    return ret;
}

void CommandBuffer::waitExec(uint64_t waitFor)
{
    if(!isCommandBufferOk()) return;
    if(!m_IsRunning){
        return;
    }

    auto result = m_device->waitForFences(1, &m_Fence, VK_TRUE, waitFor);
    if (result == vk::Result::eTimeout) {
        SPDLOG_WARN("wait from command complete timeout!");
        return;
    }else if(result == vk::Result::eSuccess){
        m_IsRunning = false;
    }
}

void CommandBuffer::reset()
{
    if(!isCommandBufferOk()) return;
    if(!isExecCompleted()){
        SPDLOG_ERROR("latest vulkan command buffer submited but not complete!");
        return;
    }
    m_CommandBuffer->reset(vk::CommandBufferResetFlagBits::eReleaseResources);
    m_IsRecording = false;
    SPDLOG_INFO("command buffer explicit reset");
}

void CommandBuffer::record(std::shared_ptr<CommandBuffer> secondaryCmdBuf)
{
    if(!secondaryCmdBuf){
        SPDLOG_ERROR(__FUNCTION__
                    " input null pointer!");
        return;
    }

    if(!this->isPrimaryLevel()){
        throw std::runtime_error(
                    "secondary command buffer can not record other secondary command buffer!");
    }

    if(!secondaryCmdBuf->isSecondaryLevel()){
        throw std::runtime_error(
                    __FUNCTION__
                    " only not allow primary command buffer as input!");
    }

//    if(!this->isSameCmdPool(*secondaryCmdBuf)){
//        throw std::runtime_error(
//                    "The commandpool of secondary command buffer is not same with primary command buffer!");
//    }

    if(!this->isSameQueue(*secondaryCmdBuf)){
        throw std::runtime_error(
                    "Queue of secondary command buffer is not same with primary command buffer!");
    }

    if(!isCommandBufferOk()) return;

    if(!isExecCompleted()){
        SPDLOG_ERROR("latest vulkan command buffer submited but not complete!");
        return;
    }

    if(!m_IsRecording){
        begin();
    }

    auto vkcmdbuf_s = secondaryCmdBuf->getVKCmdBuf();
    this->m_CommandBuffer->executeCommands(
                1,
                &vkcmdbuf_s
                );
}

void CommandBuffer::record(kpext::IRecordCommands * irecord)
{
    if(!isCommandBufferOk() || !irecord) return;
    if(!isExecCompleted()){
        SPDLOG_ERROR("latest vulkan command buffer submited but not complete!");
        return;
    }

    if(!m_IsRecording){
        begin();
    }

    if(!irecord){
        SPDLOG_ERROR(__FUNCTION__ " input null pointer!");
        return;
    }

    irecord->record(*m_CommandBuffer);
    SPDLOG_DEBUG("kpext::IRecordCommands recorded");
}

#ifdef KPEXT_USE_KOMPUTE
void CommandBuffer::record(std::shared_ptr<kp::OpBase> op)
{

    if(!isCommandBufferOk()) return;
    if(!isExecCompleted()){
        SPDLOG_ERROR("latest vulkan command buffer submited but not complete!");
        return;
    }

    if(!m_IsRecording){
        begin();
    }

    if(!op){
        SPDLOG_ERROR(__FUNCTION__ " input null pointer!");
        return;
    }
    op->record(*m_CommandBuffer);
    SPDLOG_DEBUG("kp::OpBase recorded");

}

void CommandBuffer::record(kp::OpBase * op)
{
    if(!isCommandBufferOk()) return;
    if(!isExecCompleted()){
        SPDLOG_ERROR("latest vulkan command buffer submited but not complete!");
        return;
    }

    if(!m_IsRecording){
        begin();
    }

    if(!op){
        SPDLOG_ERROR(__FUNCTION__ " input null pointer!");
        return;
    }
    op->record(*m_CommandBuffer);
    SPDLOG_DEBUG("kp::OpBase recorded");
}
#endif


void CommandBuffer::createCommandPool(vk::CommandPoolCreateFlags flag)
{

    if(!(m_device && m_queue)) return;

    vk::CommandPoolCreateInfo commandPoolInfo(flag,
                                              m_queueIndex);

    auto cmdpool = std::make_shared<vk::CommandPool>();
    auto result = m_device->createCommandPool(
                &commandPoolInfo, nullptr, cmdpool.get()
                );
    if(result != vk::Result::eSuccess){
        SPDLOG_ERROR("vulkan commandpool create failed!");
        return;
    }

    m_CommandPool = cmdpool;
    m_FreeCommandPool = true;

    SPDLOG_DEBUG("vulkan commandpool created");
}

void CommandBuffer::createCommandBuffer()
{
    if(!(m_device && m_queue && m_CommandPool)) return;

    vk::CommandBufferAllocateInfo commandBufferAllocateInfo(
                *m_CommandPool, m_bufLevel, 1);

    auto cmdbuf = std::make_shared<vk::CommandBuffer>();
    auto result = m_device->allocateCommandBuffers(
                &commandBufferAllocateInfo,
                cmdbuf.get());
    if(result != vk::Result::eSuccess){
        SPDLOG_ERROR("vulkan commandbuffer create failed!");
        return;
    }

    m_CommandBuffer = cmdbuf;
    m_FreeCommandBuffer = true;

    SPDLOG_DEBUG("vulkan commandbuffer created");
}
